#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Datetime: 2021/10/30 15:53
# @Author  : CHEN Wang
# @Site    : 
# @File    : crypto_timing_signal_generator.py
# @Software: PyCharm

"""
脚本说明: 指标根据信号转换方法转变为信号
"""

import os
import pandas as pd
from pylab import mpl
from quant_researcher.quant.factors.factor_preprocess.preprocess import ts_percentile_rank_score, ts_diff, ts_percentile_rank_signal
from quant_researcher.quant.factors.factor_preprocess.preprocess import ts_preprocess_detrend_denoise, ts_threshold_signal
from quant_researcher.quant.project_tool.time_tool import date_shifter
from quant_researcher.quant.project_tool.localize import TEST_DIR, DATA_DIR
from quant_researcher.quant.datasource_fetch.crypto_api.glassnode import get_prices, get_ret, all_http, get_indicators
from quant_researcher.quant.datasource_fetch.crypto_api import sanbase
from quant_researcher.quant.datasource_fetch.crypto_api.self_defined import onchain_metrics_list, trading_metrics_list, social_metrics_list
from quant_researcher.quant.project_tool.logger.my_logger import LOG

mpl.rcParams['font.sans-serif'] = ['SimHei']  # 设置字体为黑体
mpl.rcParams['axes.unicode_minus'] = False  # 解决中文字体负号显示不正常问题

self_defined_metrics = onchain_metrics_list + trading_metrics_list + social_metrics_list
social_metrics = sanbase.get_social_metrics_list(asset='bitcoin')
# 所有指标
all_factor_list = list(all_http.keys()) + self_defined_metrics + social_metrics


def timing_signal_generator(factor_name, asset, start_date, end_date, timing_signal_method, if_print=False):
    """
    根据信号转换方法，将指标转换为信号

    :param factor_name:
    :param asset:
    :param start_date:
    :param end_date:
    :param timing_signal_method:
    :return:
    """

    # 获取择时信号转换方法
    factor_type = timing_signal_method['factor_type']
    preprocess = timing_signal_method['preprocess']
    signal_method = timing_signal_method['signal_method']
    signal_type = timing_signal_method['signal_type']
    bins = timing_signal_method['bins']
    if isinstance(bins, str):
        bins = [float(i) for i in bins[1:-1].split(', ')]
    positive = timing_signal_method['positive']
    left_right = timing_signal_method['left_right']

    # 获取指标数据
    if factor_name in self_defined_metrics:
        if factor_name in onchain_metrics_list:
            file_path1 = os.path.join(DATA_DIR, f'onchain_data')
        elif factor_name in trading_metrics_list:
            file_path1 = os.path.join(DATA_DIR, f'trading_data')
        elif factor_name in social_metrics_list:
            file_path1 = os.path.join(DATA_DIR, f'social_data')
        else:
            raise NotImplementedError
        file_name1 = os.path.join(file_path1, f'{factor_name}')
        origin_factor_df = pd.read_csv(f'{file_name1}.csv', index_col='end_date')
        origin_factor_df = origin_factor_df.loc[start_date:end_date, :]
    elif factor_name in social_metrics:
        file_path1 = os.path.join(DATA_DIR, f'social_data')
        file_name1 = os.path.join(file_path1, f'{factor_name}')
        origin_factor_df = pd.read_csv(f'{file_name1}.csv', index_col='end_date')
        origin_factor_df = origin_factor_df.loc[start_date:end_date, :]
    else:
        origin_factor_df = get_indicators(indic_name=factor_name, asset=asset, start_date=start_date, end_date=end_date)

    if origin_factor_df is None:
        LOG.info(f'{asset}对应的{factor_name}数据无法获取')
        return
    if len(origin_factor_df.columns) > 1:
        LOG.info(f'{factor_name}数据包含多列，需要进一步指定')
        return

    origin_factor_series = origin_factor_df[factor_name]
    origin_factor_series = origin_factor_series.fillna(method='ffill')  # 指标中间缺失的需要往后填充

    # 判断指标的类型，并相应转换
    if factor_type[:6] == 'origin':
        factor_series = origin_factor_series.copy()
    elif factor_type[:4] == 'diff':  # 计算指标差分
        if factor_type[:6] == 'diff_d':
            factor_series = ts_diff(origin_factor_series, period=1)
        elif factor_type[:6] == 'diff_w':
            factor_series = ts_diff(origin_factor_series, period=7)
        elif factor_type[:6] == 'diff_m':
            factor_series = ts_diff(origin_factor_series, period=30)
        factor_series = factor_series[factor_series[~factor_series.isnull()].index[0]:]  # 剔除因为差分导致的NAN
    else:
        raise NotImplementedError

    denoise_ma7_data = ts_preprocess_detrend_denoise(factor_series, 'whole', if_detrend=False, if_denoise=True, denoise_method='ma', denoise_kwargs={'periods': 7, 'min_periods': 7})
    denoise_ma30_data = ts_preprocess_detrend_denoise(factor_series, 'whole', if_detrend=False, if_denoise=True, denoise_method='ma', denoise_kwargs={'periods': 30, 'min_periods': 30})

    if preprocess == 'denoise_hp':
        if (signal_method == 'roll180_pct'):
            rolling_denoise_hp = ts_percentile_rank_score(factor_series, way='rolling', rank_method='quantile', periods=180, min_periods=180, positive=True, scale=100, preprocess=True, if_detrend=False, if_denoise=True)
            rank_score = rolling_denoise_hp
            rolling_denoise_data = ts_preprocess_detrend_denoise(factor_series, 'rolling', periods=180, min_periods=180, if_detrend=False, if_denoise=True)
            threshold_data = rolling_denoise_data.copy()
        if (signal_method == 'roll365_pct'):
            rolling_denoise_hp = ts_percentile_rank_score(factor_series, way='rolling', rank_method='quantile', periods=365, min_periods=365, positive=True, scale=100, preprocess=True, if_detrend=False, if_denoise=True)
            rank_score = rolling_denoise_hp
            rolling_denoise_data = ts_preprocess_detrend_denoise(factor_series, 'rolling', periods=365, min_periods=365, if_detrend=False, if_denoise=True)
            threshold_data = rolling_denoise_data.copy()
        elif (signal_method == 'roll730_pct'):
            rolling_denoise_hp = ts_percentile_rank_score(factor_series, way='rolling', rank_method='quantile', periods=730, min_periods=730, positive=True, scale=100, preprocess=True, if_detrend=False, if_denoise=True)
            rank_score = rolling_denoise_hp
            rolling_denoise_data = ts_preprocess_detrend_denoise(factor_series, 'rolling', periods=730, min_periods=730, if_detrend=False, if_denoise=True)
            threshold_data = rolling_denoise_data.copy()
        elif (signal_method == 'roll1460_pct'):
            rolling_denoise_hp = ts_percentile_rank_score(factor_series, way='rolling', rank_method='quantile', periods=1460, min_periods=1460, positive=True, scale=100, preprocess=True, if_detrend=False, if_denoise=True)
            rank_score = rolling_denoise_hp
            rolling_denoise_data = ts_preprocess_detrend_denoise(factor_series, 'rolling', periods=1460, min_periods=1460, if_detrend=False, if_denoise=True)
            threshold_data = rolling_denoise_data.copy()
        elif (signal_method == 'expand_pct'):
            expanding_denoise_hp = ts_percentile_rank_score(factor_series, way='expanding', rank_method='quantile', min_periods=365, positive=True, scale=100, preprocess=True, if_detrend=False, if_denoise=True)
            rank_score = expanding_denoise_hp
            expanding_denoise_data = ts_preprocess_detrend_denoise(factor_series, 'expanding', min_periods=365, if_detrend=False, if_denoise=True)
            threshold_data = expanding_denoise_data.copy()
        elif (signal_method == 'full_history'):
            whole_denoise_hp = ts_percentile_rank_score(factor_series, way='whole', rank_method='quantile', positive=True, scale=100, preprocess=True, if_detrend=False, if_denoise=True)
            rank_score = whole_denoise_hp
            whole_denoise_data = ts_preprocess_detrend_denoise(factor_series, 'whole', if_detrend=False, if_denoise=True)
            threshold_data = whole_denoise_data.copy()

    if preprocess == 'denoise_ma7':
        if (signal_method == 'roll180_pct'):
            rolling_denoise_ma7 = ts_percentile_rank_score(factor_series, way='rolling', rank_method='quantile',  periods=180, min_periods=180, positive=True, scale=100, preprocess=True, if_detrend=False, if_denoise=True, denoise_method='ma', denoise_kwargs={'periods': 7, 'min_periods': 7})
            rank_score = rolling_denoise_ma7
            threshold_data = denoise_ma7_data.copy()
        if (signal_method == 'roll365_pct'):
            rolling_denoise_ma7 = ts_percentile_rank_score(factor_series, way='rolling', rank_method='quantile',  periods=365, min_periods=365, positive=True, scale=100, preprocess=True, if_detrend=False, if_denoise=True, denoise_method='ma', denoise_kwargs={'periods': 7, 'min_periods': 7})
            rank_score = rolling_denoise_ma7
            threshold_data = denoise_ma7_data.copy()
        elif (signal_method == 'roll730_pct'):
            rolling_denoise_ma7 = ts_percentile_rank_score(factor_series, way='rolling', rank_method='quantile', periods=730, min_periods=730, positive=True, scale=100, preprocess=True, if_detrend=False, if_denoise=True, denoise_method='ma', denoise_kwargs={'periods': 7, 'min_periods': 7})
            rank_score = rolling_denoise_ma7
            threshold_data = denoise_ma7_data.copy()
        elif (signal_method == 'roll1460_pct'):
            rolling_denoise_ma7 = ts_percentile_rank_score(factor_series, way='rolling', rank_method='quantile', periods=1460, min_periods=1460, positive=True, scale=100, preprocess=True, if_detrend=False, if_denoise=True, denoise_method='ma', denoise_kwargs={'periods': 7, 'min_periods': 7})
            rank_score = rolling_denoise_ma7
            threshold_data = denoise_ma7_data.copy()
        elif (signal_method == 'expand_pct'):
            expanding_denoise_ma7 = ts_percentile_rank_score(factor_series, way='expanding', rank_method='quantile', min_periods=365, positive=True, scale=100, preprocess=True, if_detrend=False, if_denoise=True, denoise_method='ma', denoise_kwargs={'periods': 7, 'min_periods': 7})
            rank_score = expanding_denoise_ma7
            threshold_data = denoise_ma7_data.copy()
        elif (signal_method == 'full_history'):
            whole_denoise_ma7 = ts_percentile_rank_score(factor_series, way='whole', rank_method='quantile', positive=True, scale=100, preprocess=True, if_detrend=False, if_denoise=True, denoise_method='ma', denoise_kwargs={'periods': 7, 'min_periods': 7})
            rank_score = whole_denoise_ma7
            threshold_data = denoise_ma7_data.copy()

    if preprocess == 'denoise_ma30':
        if (signal_method == 'roll180_pct'):
            rolling_denoise_ma30 = ts_percentile_rank_score(factor_series, way='rolling', rank_method='quantile',  periods=180, min_periods=180, positive=True, scale=100, preprocess=True, if_detrend=False, if_denoise=True, denoise_method='ma', denoise_kwargs={'periods': 30, 'min_periods': 30})
            rank_score = rolling_denoise_ma30
            threshold_data = denoise_ma30_data.copy()
        if (signal_method == 'roll365_pct'):
            rolling_denoise_ma30 = ts_percentile_rank_score(factor_series, way='rolling', rank_method='quantile',  periods=365, min_periods=365, positive=True, scale=100, preprocess=True, if_detrend=False, if_denoise=True, denoise_method='ma', denoise_kwargs={'periods': 30, 'min_periods': 30})
            rank_score = rolling_denoise_ma30
            threshold_data = denoise_ma30_data.copy()
        elif (signal_method == 'roll730_pct'):
            rolling_denoise_ma30 = ts_percentile_rank_score(factor_series, way='rolling', rank_method='quantile', periods=730, min_periods=730, positive=True, scale=100, preprocess=True, if_detrend=False, if_denoise=True, denoise_method='ma', denoise_kwargs={'periods': 30, 'min_periods': 30})
            rank_score = rolling_denoise_ma30
            threshold_data = denoise_ma30_data.copy()
        elif (signal_method == 'roll1460_pct'):
            rolling_denoise_ma30 = ts_percentile_rank_score(factor_series, way='rolling', rank_method='quantile', periods=1460, min_periods=1460, positive=True, scale=100, preprocess=True, if_detrend=False, if_denoise=True, denoise_method='ma', denoise_kwargs={'periods': 30, 'min_periods': 30})
            rank_score = rolling_denoise_ma30
            threshold_data = denoise_ma30_data.copy()
        elif (signal_method == 'expand_pct'):
            expanding_denoise_ma30 = ts_percentile_rank_score(factor_series, way='expanding', rank_method='quantile', min_periods=365, positive=True, scale=100, preprocess=True, if_detrend=False, if_denoise=True, denoise_method='ma', denoise_kwargs={'periods': 30, 'min_periods': 30})
            rank_score = expanding_denoise_ma30
            threshold_data = denoise_ma30_data.copy()
        elif (signal_method == 'full_history'):
            whole_denoise_ma30 = ts_percentile_rank_score(factor_series, way='whole', rank_method='quantile', positive=True, scale=100, preprocess=True, if_detrend=False, if_denoise=True, denoise_method='ma', denoise_kwargs={'periods': 30, 'min_periods': 30})
            rank_score = whole_denoise_ma30
            threshold_data = denoise_ma30_data.copy()

    if preprocess == 'detrend_denoise':
        if (signal_method == 'roll180_pct'):
            rolling_detrend_denoise = ts_percentile_rank_score(factor_series, way='rolling', rank_method='quantile', periods=180, min_periods=180, positive=True, scale=100, preprocess=True, if_detrend=True, detrend_method='ma', detrend_kwargs={'periods': 120}, if_denoise=True)
            rank_score = rolling_detrend_denoise
            rolling_detrend_denoise_data = ts_preprocess_detrend_denoise(factor_series, 'rolling', periods=180, min_periods=180, if_detrend=True, detrend_method='ma', detrend_kwargs={'periods': 120}, if_denoise=True)
            threshold_data = rolling_detrend_denoise_data.copy()
        elif (signal_method == 'roll365_pct'):
            rolling_detrend_denoise = ts_percentile_rank_score(factor_series, way='rolling', rank_method='quantile', periods=365, min_periods=365, positive=True, scale=100, preprocess=True, if_detrend=True, detrend_method='ma', detrend_kwargs={'periods': 120}, if_denoise=True)
            rank_score = rolling_detrend_denoise
            rolling_detrend_denoise_data = ts_preprocess_detrend_denoise(factor_series, 'rolling', periods=365, min_periods=365, if_detrend=True, detrend_method='ma', detrend_kwargs={'periods': 120}, if_denoise=True)
            threshold_data = rolling_detrend_denoise_data.copy()
        elif (signal_method == 'roll730_pct'):
            rolling_detrend_denoise = ts_percentile_rank_score(factor_series, way='rolling', rank_method='quantile', periods=730, min_periods=730, positive=True, scale=100, preprocess=True, if_detrend=True, detrend_method='ma', detrend_kwargs={'periods': 120}, if_denoise=True)
            rank_score = rolling_detrend_denoise
            rolling_detrend_denoise_data = ts_preprocess_detrend_denoise(factor_series, 'rolling', periods=730, min_periods=730, if_detrend=True, detrend_method='ma', detrend_kwargs={'periods': 120}, if_denoise=True)
            threshold_data = rolling_detrend_denoise_data.copy()
        elif (signal_method == 'roll1460_pct'):
            rolling_detrend_denoise = ts_percentile_rank_score(factor_series, way='rolling', rank_method='quantile', periods=1460, min_periods=1460, positive=True, scale=100, preprocess=True, if_detrend=True, detrend_method='ma', detrend_kwargs={'periods': 120}, if_denoise=True)
            rank_score = rolling_detrend_denoise
            rolling_detrend_denoise_data = ts_preprocess_detrend_denoise(factor_series, 'rolling', periods=1460, min_periods=1460, if_detrend=True, detrend_method='ma', detrend_kwargs={'periods': 120}, if_denoise=True)
            threshold_data = rolling_detrend_denoise_data.copy()
        elif (signal_method == 'expand_pct'):
            expanding_detrend_denoise = ts_percentile_rank_score(factor_series, way='expanding', rank_method='quantile', min_periods=365, positive=True, scale=100, preprocess=True, if_detrend=True, detrend_method='ma', detrend_kwargs={'periods': 120}, if_denoise=True)
            rank_score = expanding_detrend_denoise
            expanding_detrend_denoise_data = ts_preprocess_detrend_denoise(factor_series, 'expanding', min_periods=365, if_detrend=True, detrend_method='ma', detrend_kwargs={'periods': 120}, if_denoise=True)
            threshold_data = expanding_detrend_denoise_data.copy()
        elif (signal_method == 'full_history'):
            whole_detrend_denoise = ts_percentile_rank_score(factor_series, way='whole', rank_method='quantile', positive=True, scale=100, preprocess=True, if_detrend=True, detrend_method='ma', detrend_kwargs={'periods': 120}, if_denoise=True)
            rank_score = whole_detrend_denoise
            whole_detrend_denoise_data = ts_preprocess_detrend_denoise(factor_series, 'whole', if_detrend=True, detrend_method='ma', detrend_kwargs={'periods': 120}, if_denoise=True)
            threshold_data = whole_detrend_denoise_data.copy()

    if preprocess == 'False':
        if (signal_method == 'roll180_pct'):
            rolling_False = ts_percentile_rank_score(factor_series, way='rolling', rank_method='quantile', periods=180, min_periods=180, positive=True, scale=100, preprocess=False)
            rank_score = rolling_False
            threshold_data = factor_series.copy()
        elif (signal_method == 'roll365_pct'):
            rolling_False = ts_percentile_rank_score(factor_series, way='rolling', rank_method='quantile', periods=365, min_periods=365, positive=True, scale=100, preprocess=False)
            rank_score = rolling_False
            threshold_data = factor_series.copy()
        elif (signal_method == 'roll730_pct'):
            rolling_False = ts_percentile_rank_score(factor_series, way='rolling', rank_method='quantile', periods=730, min_periods=730, positive=True, scale=100, preprocess=False)
            rank_score = rolling_False
            threshold_data = factor_series.copy()
        elif (signal_method == 'roll1460_pct'):
            rolling_False = ts_percentile_rank_score(factor_series, way='rolling', rank_method='quantile', periods=1460, min_periods=1460, positive=True, scale=100, preprocess=False)
            rank_score = rolling_False
            threshold_data = factor_series.copy()
        elif (signal_method == 'expand_pct'):
            expanding_False = ts_percentile_rank_score(factor_series, way='expanding', rank_method='quantile', min_periods=365, positive=True, scale=100, preprocess=False)
            rank_score = expanding_False
            threshold_data = factor_series.copy()
        elif (signal_method == 'full_history'):
            whole_False = ts_percentile_rank_score(factor_series, way='whole', rank_method='quantile', positive=True, scale=100, preprocess=False)
            rank_score = whole_False
            threshold_data = factor_series.copy()

    # 对得分指标先转变成买卖信号
    if signal_type in ['2', '3', 2, 3]:
        signal = ts_percentile_rank_signal(rank_score, is_score=True, positive=positive, bins=bins, left_right=left_right)
    elif signal_type == 'holding_pct':
        signal = rank_score / 100
    elif signal_type == 'threshold':
        signal = ts_threshold_signal(threshold_data, positive=positive, bins=bins, left_right=left_right)
    else:
        raise NotImplementedError

    if signal.isnull().all():  # 信号全为NaN或者signal为空的series, 跳过该测试情形
        print(f'{preprocess}-{signal_method}-{signal_type}-{bins}-{positive}-{left_right}情形下信号全为NaN, 无法测试')
        return

    # signal 需要滞后一期使用
    latest_date = date_shifter(signal.index[-1], step='days', how_many=1)
    signal[latest_date] = None  # 新增一个最新日期，shift不会自动生成
    signal = signal.shift(1)
    signal.dropna(inplace=True)

    if if_print:
        # 信号保存本地
        file_path = os.path.join(TEST_DIR, '单指标信号数据')
        os.makedirs(file_path, exist_ok=True)
        file_name = os.path.join(file_path, f'signal-{factor_name}-{preprocess}-{signal_method}-{signal_type}-{bins}-{positive}-{left_right}')
        signal.to_csv(f'{file_name}.csv')

    return signal, origin_factor_series


if __name__ == '__main__':
    # 测试timing_signal_generator
    asset = 'BTC'
    start_date = '2015-01-01'
    end_date = '2021-12-27'
    # social_dominance_reddit_1h_moving_average-diff_m-preprocess_denoise_hp-expand_pct-3-[0, 0.05, 0.9, 1]-True-right
    factor_name = 'social_dominance_total_1h_moving_average_change_1d'
    timing_signal_method = pd.Series(['diff_w', 'denoise_ma30', 'roll365_pct', '3', [0, 0.05, 0.9, 1], False, 'right'],
                                     index=['factor_type', 'preprocess', 'signal_method', 'signal_type', 'bins', 'positive', 'left_right'])
    timing_signal_generator(factor_name, asset, start_date, end_date, timing_signal_method)

    # Block Interval (Median)-origin-preprocess_denoise_ma7-roll730_pct-3-[0, 0.05, 0.7, 1]-True-right
    factor_name = 'Block Interval (Median)'
    timing_signal_method = pd.Series(['origin', 'denoise_ma7', 'roll730_pct', '3', [0, 0.05, 0.7, 1], True, 'right'],
                                     index=['factor_type', 'preprocess', 'signal_method', 'signal_type', 'bins', 'positive', 'left_right'])
    timing_signal_generator(factor_name, asset, start_date, end_date, timing_signal_method)

    # Illiquid Supply Shock-diff_m-preprocess_False-roll730_pct-3-[0, 0.5, 0.7, 1]-True-right
    factor_name = 'Illiquid Supply'
    timing_signal_method = pd.Series(['diff_m', 'False', 'roll730_pct', '3', [0, 0.4, 0.6, 1], True, 'left'],
                                     index=['factor_type', 'preprocess', 'signal_method', 'signal_type', 'bins', 'positive', 'left_right'])
    timing_signal_generator(factor_name, asset, start_date, end_date, timing_signal_method)